Table Of Contents

Previous topic

Potential class

Next topic

CoulombSummation class

This Page

ProductPotential class

This class defines an interaction as a product of Potentials.

Adding and multiplying potentials

The ordinary Potential defines a local interaction for \(n\) bodies (\(i,j,\ldots\)), so that the total potential energy is

\[V = \sum_{(i,j,\ldots)} v_{i,j,\ldots}.\]

Defining several such potentials (\(p\)) for the same set of atoms simply adds them together

\[V = \sum_{(i,j,\ldots)} \sum_p v^p_{i,j,\ldots}.\]

In code, this could be done for instance this way:

pot1 = pysic.Potential(...)
pot2 = pysic.Potential(...)
calc = pysic.Pysic()
calc.set_potentials( [pot1, pot2] )

However, you may also wish to multiply the potentials to obtain

\[V = \sum_{(i,j,\ldots)} \prod_p v^p_{i,j,\ldots}.\]

This can be done by wrapping the potentials to be multiplied in a ProductPotential:

pot1 = pysic.Potential(...)
pot2 = pysic.Potential(...)
prod = pysic.ProductPotential( [pot1, pot2] )
calc = pysic.Pysic()
calc.set_potentials( prod )

An immediate benefit of this functionality is the possibility to construct complicated potentials from simple building blocks without having to hardcode all the different variants.

As an example:

pot1 = pysic.Potential('power', parameters=[1,1])
pot2 = pysic.Potential('charge_pair', parameters=[1,1,1])
prod = pysic.ProductPotential([pot1,pot2])

defines the potentials

\[\begin{split}v^1_{ij} & = q_i q_j \\ v^2_{ij} & = \frac{1}{|\mathbf{r}_i - \mathbf{r}_j|} \\ V = \sum_{ij} v^1_{ij} v^2_{ij} & = \sum_{ij} \frac{q_i q_j}{|\mathbf{r}_i - \mathbf{r}_j|}.\end{split}\]

Parameterization

The ProductPotential class has mostly a similar interface as the elemental Potential class. However, the object does not store parameters itself. It merely wraps a group of potentials and these potentials are used for storing the parameters. More precisesly, the first Potential object in the list contained by the ProductPotential defines all the general parameters of the potential. We call this the leading potential in the following discussion:

# Here, pot1 is the leading potential
prod = pysic.ProductPotential( [pot1, pot2, pot3] )

Since the product potential is defined as

\[V = \sum_{(i,j,\ldots)} \prod_p v^p_{i,j,\ldots},\]

the potentials \(v^p_{i,j,\ldots}\) being multiplied need to be defined for the same group of atoms - both the number of atoms and their types must match. Also the cutoffs should be equal, since if one \(v^p = 0\), the whole product is. Because of this, the targets and cutoffs defined by the leading potential are used for the entire product.

In fact, since the cutoff and targets of the potentials forming the product besides the leading one are ignored, they need not be defined at all. Also possible Coordinator objects defining bond order factors need to be attached to the leading potential in order to have an effect.

As an example, the following code:

pot1 = pysic.Potential(..., symbols=[['H','O']])
pot2 = pysic.Potential(..., symbols=[['H','H']])
prod1 = pysic.ProductPotential( [pot1, pot2] )
prod2 = pysic.ProductPotential( [pot2, pot1] )

defines two products, prod1 and prod2, which are both a product of the potentials pot1 and pot2. However, the former, prod1, targets H-O pairs (pot1 is the leading potential) while the latter, prod2, affects H-H pairs (pot2 is the leading potential).

Furthermore, the ProductPotential defines methods for handling the general parameters similarly to the Potential class, and these methods directly affect the leading potential. That is, methods of the type:

prod = pysic.ProductPotential( [pot1, pot2] )
# method call on prod
prod.set_cutoff(8.0)
# is equivalent to a method call on the leading potential in prod
prod.get_potentials()[0].set_cutoff(8.0)

are equivalent.

To avoid changing the original potentials, the objects are copied when passed to the product. However, this also means that you are unable to modify the product by changing the component potentials after having wrapped them:

prod = pysic.ProductPotential( [pot1, pot2] )
# method call on prod
prod.set_cutoff(8.0)
# is not the same as a method call on the original pot1
pot1.set_cutoff(8.0)

For instance:

calc = pysic.Pysic()
pot1 = pysic.Potential(..., symbols=[['H','O']])   # Define a potential for H-O
calc.add_potential( pot1 )                         # Add pot1 to the calculator
pot2 = pysic.Potential(...)                        # Define another potential
prod = pysic.ProductPotential( [pot1, pot2] )      # Multiply pot1 and pot2 to make prod
prod.set_symbols( [['H','H']] )                    # Set prod to target H-H
calc.add_potential( prod )                         # Add the prod to the calculator

creates two potentials, pot1 and prod affecting H-O and H-H pairs. Although prod is constructed from pot1 and pot2, is actually contains only copies of these potentials. If prod contained the original potential objects prod.set_symbols( [['H','H']] ) would affect pot1, the leading potential, and change symbols in pot1 as well having both potentials end up targeting H-H pairs. This kind of behaviour could lead to unintentional changes in the properties of other potentials

On the other hand, this example:

calc = pysic.Pysic()
pot1 = pysic.Potential(..., symbols=[['H','O']])   # Define a potential for H-O
calc.add_potential( pot1 )                         # Add pot1 to the calculator
pot2 = pysic.Potential(...)                        # Define another potential
prod = pysic.ProductPotential( [pot1, pot2] )      # Multiply pot1 and pot2 to make prod
pot1.set_symbols( [['H','H']] )                    # Set pot1 to target H-H
calc.add_potential( prod )                         # Add the prod to the calculator

Leads to pot1 and prod affecting H-H and H-O pairs, respectively. This is because pot1.set_symbols( [['H','H']] ) only changes the original potential, not the copy stored in prod.

The physical parameters of the potential components are naturally defined separately for each potential. Therefore the ProductPotential has no methods for accessing these parameters of its constituents. Instead, one has to directly access the Potential objects themselves:

pot1 = pysic.Potential(...)                            # Define a potential
pot2 = pysic.Potential(...)                            # Define another potential
prod = pysic.ProductPotential( [pot1, pot2] )          # Multiply pot1 and pot2 to make prod
prod.get_potentials()[1].set_parameter_value('a', 1.0) # set parameter a to 1.0
pot2.set_parameter_value('a' 2.0)                      # set parameter a to 2.0

Above, the second potential in prod gets the parameter value a = 1.0. The last command changes the parameter value in pot2, but the change does not propagate to the copy stored in prod.

Full documentation of the ProductPotential class

class pysic.interactions.local.ProductPotential(potentials)[source]

Class representing an interaction obtained by multiplying several Potential objects.

Parameters:

potentials: a list of Potential objects
the potentials
accepts_target_list(targets)

Tests whether a list is suitable as a list of targets, i.e., symbols, tags, or indices and returns True or False accordingly.

A list of targets should be of the format:

targets = [[a, b], [c, d]]

where the length of the sublists must equal the number of targets.

It is not tested that the values contained in the list are valid.

Parameters:

targets: list of strings or integers
a list whose format is checked
add_indices(indices)[source]

Adds the given indices to the list of indices.

Parameters:

indices: list of integers list of additional indices on which the potential acts

add_potential(potential)[source]

Adds a potential in the product.

If another ProductPotential is given as an argument, the individual potentials in the product are added one by one to this product.

Also a list of potentials can be given. Then all the listed potentials are added to the product.

Parameters:

potential: a Potential object
the potential to be added
add_symbols(symbols)[source]

Adds the given symbols to the list of symbols.

Parameters:

symbols: list of strings list of additional symbols on which the potential acts

add_tags(tags)[source]

Adds the given tags to the list of tags.

Parameters:

tags: list of integers list of additional tags on which the potential acts

get_coordinator()[source]

Returns the Coordinator.

get_cutoff()[source]

Returns the cutoff.

get_cutoff_margin()[source]

Returns the margin for a smooth cutoff.

get_different_indices()[source]

Returns a list containing each index the potential affects once.

get_different_symbols()[source]

Returns a list containing each symbol the potential affects once.

get_different_tags()[source]

Returns a list containing each tag the potential affects once.

get_indices()[source]

Return a list of indices on which the potential acts on.

get_number_of_targets()[source]

Returns the number of targets.

get_potentials()[source]

Returns the potentials stored in the ProductPotential.

get_soft_cutoff()[source]

Returns the lower limit for a smooth cutoff.

get_symbols()[source]

Return a list of the chemical symbols (elements) on which the potential acts on.

get_tags()[source]

Return the tags on which the potential acts on.

is_multiplier()[source]

Returns a list of logical values specifying if the potentials are multipliers for a product.

The leading potential is considered not to be a multiplier, while the rest are multipliers. Therefore the returned list is [False, True, ..., True], withlength equal to the length of potentials in the product.

set_coordinator(coordinator)[source]

Sets a new Coordinator.

Parameters:

coordinator: a Coordinator object the Coordinator

set_cutoff(cutoff)[source]

Sets the cutoff to a given value.

This method affects the hard cutoff. For a detailed explanation on how to define a soft cutoff, see set_cutoff_margin().

Parameters:

cutoff: double new cutoff for the potential

set_cutoff_margin(cutoff_margin)[source]

Sets the margin for smooth cutoff to a given value.

Many potentials decay towards zero in infinity, but in a numeric simulation they are cut at a finite range as specified by the cutoff radius. If the potential is not exactly zero at this range, a discontinuity will be introduced. It is possible to avoid this by including a smoothening factor in the potential to force a decay to zero in a finite interval.

This method defines the decay interval \(r_\mathrm{hard}-r_\mathrm{soft}\). Note that if the soft cutoff value is made smaller than 0 or larger than the hard cutoff value an InvalidPotentialError is raised.

Parameters:

margin: double The new cutoff margin

set_indices(indices)[source]

Sets the list of indices to equal the given list.

Parameters:

indices: list of integers list of integers on which the potential acts

set_potentials(potentials)[source]

Sets the list of potentials for the product.

Parameters:

potentials: a list of Potential objects
the potentials
set_soft_cutoff(cutoff)[source]

Sets the soft cutoff to a given value.

For a detailed explanation on the meaning of a soft cutoff, see set_cutoff_margin(). Note that actually the cutoff margin is recorded, so changing the hard cutoff (see set_cutoff()) will also affect the soft cutoff.

Parameters:

cutoff: double The new soft cutoff

set_symbols(symbols)[source]

Sets the list of symbols to equal the given list.

Parameters:

symbols: list of strings list of element symbols on which the potential acts

set_tags(tags)[source]

Sets the list of tags to equal the given list.

Parameters:

tags: list of integers list of tags on which the potential acts